/*
 * Copyright 2014-2015 Mikhail Shugay
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.antigenomics.migmap.mutation

import com.antigenomics.migmap.pipeline.Util
import com.antigenomics.migmap.blast.Alignment
import com.antigenomics.migmap.genomic.Segment
import com.antigenomics.migmap.mapping.RegionMarkup
import groovy.transform.CompileStatic

import static com.antigenomics.migmap.mutation.MutationType.Deletion
import static com.antigenomics.migmap.mutation.MutationType.None

@CompileStatic
class MutationExtractor {
    final List<Mutation> mutations
    final int offset

    MutationExtractor(Segment segment,
                      Alignment alignment,
                      RegionMarkup regionMarkup) {
        def vResult = extract(alignment)

        vResult.mutations.each {
            it.parent = segment
            it.subRegion = regionMarkup.getSubRegion(it)
        }

        // This will be used later to recompute J and D mutations to V segment germline coords
        // First two terms are difference between read and germline coordinates, second two are
        // offset generated by indels

        // 0000000000111   11111112222222222
        // 0123456789012   34567890123456789
        // ______.......---........_____.... s
        //      _.............--..._____.... q
        //      00000000001111  111111222222
        //      01234567890123  456789012345
        //
        // sstart = 6
        // qstart = 1
        // sdelta = 3
        // qdelta = 2
        //
        //
        // 22 + x = 26
        //
        // offset = sstart - qstart = 5
        //
        // 22 + offset(5) - sdelta(3) + qdelta(2) = 26

        this.offset = alignment.sstart - alignment.qstart - vResult.sDelta + vResult.qDelta

        this.mutations = vResult.mutations
    }

    static MutationExtractorResult extract(Alignment alignment) {
        def mutations = new ArrayList<Mutation>()
        int start = -1, end = -1
        int qdelta = 0, sdelta = 0
        def type = None

        def writeMutation = {
            if (type != None) {
                mutations.add(new Mutation(
                        type,
                        start + alignment.sstart - sdelta, end + alignment.sstart - sdelta,
                        start + alignment.qstart - qdelta, end + alignment.qstart - qdelta,
                        alignment.sseq[start..<end], alignment.qseq[start..<end])
                )
            }
        }

        for (int i = 0; i < alignment.qseq.length(); i++) {
            char q = alignment.qseq.charAt(i),
                 s = alignment.sseq.charAt(i)

            if (q == Util.GAP) {
                if (type != Deletion) {
                    writeMutation()
                    type = Deletion
                    start = i
                    end = i + 1
                } else {
                    end++
                }

                qdelta++
            } else if (s == Util.GAP) {
                if (type != MutationType.Insertion) {
                    writeMutation()
                    type = MutationType.Insertion
                    start = i
                    end = i + 1
                } else {
                    end++
                }

                sdelta++
            } else if (s != q) {
                // We store substitutions one-by-one
                // This is to facilitate post analysis (perhaps should be changed)
                if (type != MutationType.Substitution) {
                    writeMutation()
                }
                type = MutationType.Substitution
                start = i
                end = i + 1
                writeMutation()
                type = None
            } else {
                // no mutation here, flush
                writeMutation()
                type = None
            }
        }

        // flush last
        writeMutation()

        new MutationExtractorResult(mutations, qdelta, sdelta)
    }

    void extractJ(Segment segment,
                  Alignment alignment) {
        extract(alignment).mutations.each {
            it.parent = segment
            it.subRegion = (it.start > segment.referencePoint + 3) ? SubRegion.FR4 : SubRegion.CDR3
            it.start = it.startInRead + offset
            it.end = it.endInRead + offset
            this.mutations.add(it)
        }
    }

    void extractD(Segment segment,
                  Alignment alignment) {
        extract(alignment).mutations.each {
            it.parent = segment
            it.subRegion = SubRegion.CDR3
            it.start = it.startInRead + offset
            it.end = it.endInRead + offset
            this.mutations.add(it)
        }
    }

    static class MutationExtractorResult {
        final List<Mutation> mutations
        final int qDelta, sDelta

        MutationExtractorResult(List<Mutation> mutations, int qDelta, int sDelta) {
            this.mutations = mutations
            this.qDelta = qDelta
            this.sDelta = sDelta
        }
    }
}
